using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace Dsp
{
    /// <summary>
    /// Writes a *mono* raw file. Supports different sample formats:
    /// * S_PCM08: 08-bit Unsigned Linear PCM
    /// * U_PCM08: 08-bit Signed   Linear PCM
    /// * S_PCM16: 16-bit Signed   Linear PCM
    /// * U_PCM16: 16-bit Unsigned Linear PCM
    /// * S_PCM32: 24-bit Signed   Linear PCM
    /// * U_PCM32: 24-bit Unsigned Linear PCM
    /// * FLOAT32: 32-bit Floating Point (IEEE 754)
    /// * FLOAT64: 64-bit Floating Point (IEEE 754)
    /// 
    /// In the case of PCM format, the input samples are clipped betwen [-1, +1) prior
    /// to convert them in 8, 16 or 24 bit integer data types. Therefore the precision 
    /// in PCM formats get larger with number of bits but the dynamic range is limited 
    /// between [-1, +1). If you want to have to record signals with a greater dynamic 
    /// range and better SNR use FLT32 format.
    /// 
    /// References:
    /// http://www.sonicspot.com/guide/wavefiles.html
    /// </summary>
    public class RawFileWriter 
    {
        private RawFileFormat _format;
        private string        _filename;           // if aplicable
        private Stream        _stream;             // generic stream with CanSeek := true
        private BinaryWriter  _writer;
        private uint          _sampleCount;
        private bool          _closed;
        private bool          _clipped;            // Any sample was clipped?

        #region Constructors
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="filename">File name created wiwth FileMode.Create (OVERWRITE MODE!)</param>
        /// <param name="format">Sample format</param>
        /// <param name="fs">Sampling frequency</param>
        public RawFileWriter(string filename, RawFileFormat format)
        {
            _filename = filename;

            // FileMode.Create    => Overwrite!
            // FileMode.CreateNew => Don't overwrite!
            _stream = new FileStream(_filename, FileMode.Create);
            //_stream = new FileStream(_filename, FileMode.CreateNew);

            Constructor(_stream, format);
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="stream">Any Stream Class derived object with the property CanSeek set to true
        /// like files and memory streams but it isn't valid sockets, serial ports or the like streams</param>
        /// <param name="format">Sample format</param>
        /// <param name="fs">Sampling frequency</param>
        public RawFileWriter(Stream stream, RawFileFormat format)
        {
            Constructor(stream, format);
        }


        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="type">Access type; for writting or reading</param>
        /// <param name="filename">File name to CREATE for write</param>
        private void Constructor(Stream stream, RawFileFormat format)
        {
            _stream      = stream;
            _format      = format;
            _sampleCount = 0;
            _closed      = false;
            _clipped     = false;
            _writer      = new BinaryWriter(_stream);
        }

        #endregion

        
        /// <summary>
        /// Writes audio samples in the RAW file. If the sample format is PCM then a clip 
        /// operation between (+1, -1) must be performed because samples outside this range
        /// can't be represented with this fixed point format.
        /// </summary>
        /// <param name="buffer">Array with data</param>
        /// <param name="offset">Offset in the array</param>
        /// <param name="count">Number of samples that will be written</param>
        public int XferData(double[] buffer, int offset, int count)
        {
            int i;

            // Audio data must be between [-1, +1] for PCM formats. If not, must be clipped
            // thus, in PCM format, the dynamic range in limited to [-1, +1)
            if ((_format != RawFileFormat.FLOAT32) && 
                (_format != RawFileFormat.FLOAT64))
            {
                for (i = offset; i < offset + count; i++)
                {
                    if (buffer[i] > +1.0) { buffer[i] = +1.0; _clipped = true; }
                    if (buffer[i] < -1.0) { buffer[i] = -1.0; _clipped = true; }
                }
            }

            // samples in different formats
            byte   u08;
            sbyte  s08;
            ushort u16;
            short  s16;
            int    s24;
            byte   s24_1;
            byte   s24_2;
            byte   s24_3;
            float  s32;
            double s64;


            int items = 0;

            for (i = offset; i < offset + count; i++)
            {
                switch (_format)
                {
                    case RawFileFormat.U_PCM08: 
                        u08 = (byte) (((1 + buffer[i]) / 2) * 255); 
                        _writer.Write(u08); 
                        break;

                    case RawFileFormat.S_PCM08:
                        s08 = (sbyte)(buffer[i] * 127);
                        _writer.Write(s08);
                        break;

                    case RawFileFormat.U_PCM16:
                        u16 = (ushort)(((1 + buffer[i]) / 2) * 65535);
                        _writer.Write(u16);
                        break;

                    case RawFileFormat.S_PCM16: 
                        s16 = (short) (buffer[i] * 0032767); 
                        _writer.Write(s16); 
                        break;

                    case RawFileFormat.U_PCM24:
                        break;

                    case RawFileFormat.S_PCM24:
                        // only LSB 24-bit take effect!
                        s24   = (int)  (buffer[i] * 8388607); 

                        s24_1 = (byte) ((s24 & 0x000000FF)      );
                        s24_2 = (byte) ((s24 & 0x0000FF00) >> 08);
                        s24_3 = (byte) ((s24 & 0x00FF0000) >> 16);
                        
                        // WAV File Format => little endian!
                        _writer.Write(s24_1);
                        _writer.Write(s24_2);
                        _writer.Write(s24_3);
                        break;

                    case RawFileFormat.FLOAT32: 
                        s32 = (float) (buffer[i]); 
                        _writer.Write(s32); 
                        break;


                    case RawFileFormat.FLOAT64:
                        s64 = (double)(buffer[i]);
                        _writer.Write(s64);
                        break;
                }

                
                _sampleCount++;
                items++;
            }

            return items;
        }

        #region Properties
        public bool   Closed       { get { return _closed;  } }
        public bool   Clipped      { get { return _clipped; } }
        public Stream Stream       { get { return _stream;  } }
        public RawFileFormat SampleFormat { get { return _format;  } }
        #endregion


        /// <summary>
        /// Finish writting the file header to achieve a coherent WAV header and then close the 
        /// opened stream (file) 
        /// </summary>
        public void Close()
        {
            _writer.Close();
            _stream.Close();
            _closed = true;
        }
    }
}